//: [Previous](@previous)

import Foundation
//: ## 结构体
/**
 - 在Swift标准库中, 绝大多数的公开类型都是结构体, 而枚举和类只占一小部分
   1.比如Bool, Int, Double, String, Array, Dictionary等常见类型都是结构体
 - 所有的结构体都有一个编译器自动生成的初始化器(也可以叫:initializer, 初始化方法, 构造器, 构造方法)
  1.调用这个初始化器(初始化方法的时候,)会传入所有的成员值,用以初始化所有的成员(这些成员在结构体里面,规范的叫法叫做存储属性,Story Property)
  2.编译器会根据情况,可能会为结构体生成多个初始化器,宗旨是:保证所有成员都有初始值
 */
func struct1(){
// ---------------------------------------------
    //举例:
    struct Date {
        var year: Int
        var month: Int
        var day: Int
    }
    var date = Date(year: 2024, month: 5, day: 20)// 这个就是结构体自带的初始化方法

    //举例2:但凡有一个成员没有初始值,初始化器都会报错
    struct Point1 {
        var x: Int
        var y: Int
    }
    //系统的初始化器一般有以下4种
    var pp1 = Point1(x: 10, y: 2)
    //var pp1 = Point1(x:10) //y没有初始值,报错
    //var pp1 = Point1(y:20) //x没有初始值,报错
    //var pp11 = Point1() //报错

    //举例3:写结构体的时候给了成员初始值,再初始化的时候这个成员就可以不付初始值
    struct Point2 {
        var x: Int = 10
        var y: Int
    }
    var pp2 = Point2(x: 10, y: 2)
    //var pp1 = Point1(x:10) //y没有初始值,报错
    var pp3 = Point2(y:20) //x在方法里面已经有了初始化值,所以在这里初始化的时候运行不赋值,不报错

    //举例4: 带有可选项的成员变量,因为可选项都是有个默认值nil的,所以相当于该可选项成员变量已经有写了初始值
    struct Point3 {
        var x: Int?
        var y: Int?
    }
    var PP4 = Point3() //可以运行,不报错
// ---------------------------------------------
}

//: ## 自定义初始化器
// 一旦在定义结构体时 自定义了初始化器, 编译器就不会再帮它自动生成其他初始化器

struct Point {
    var x: Int = 0
    var y: Int = 0
    
    //这个就是自定义初始化器,因为自定义了,所以这个结构体不再拥有系统自带的其他初始化器,比如struct2(x:), struct2(y:), struct2()
    init(x: Int, y: Int) {
        self.x = x
        self.y = y
    }
}
//    var x = Point(x: 20) //会报错

//以下2段代码(Point4和Point5),完全等效.通过汇编可以看出实现的原理一样
struct Point4 {
    var x: Int = 0
    var y: Int = 0
}
var p4 = Point4()

struct Point5 {
    var x: Int
    var y: Int
    init(){
        self.x = 0
        self.y = 0
    }
}
var p5 = Point5()

// 结构体的内部结构
struct Point6 {
    var x: Int = 3
    var y: Int = 2
    var origin: Bool = true
}
print(MemoryLayout<Point6>.size) //17 x和y都是Int都是8个字节,Bool是1个字节,所以实际用到了17个字节
print(MemoryLayout<Point6>.stride) //24 最小内存对齐是8,所以最终占用内存是24个字节
print(MemoryLayout<Point6>.alignment) //8 最小内存对齐是8个字节


//: ## 类

/**
 类的定义和结构体类似,但编译器并没有为类自动生成可以传入成员值的初始化器,而且成员真正的初始化是在无参初始化器调用的时候才初始化的(是没有可以传入成员值的初始化器,假如成员已经有写了初始值,编译器还是会生成无参的初始化器的,就是那个后面只有一个括号的)
 */

//成员都有初始值,可以使用无参初始化器
class Point1 {
    var x: Int = 0
    var y: Int = 0
}//因为这个类的两个成员只都已经写有初始值,所以可以直接使用无参的初始化器
var p1 = Point1()
//下面3种,在类中正常是不会存在的
//    var p1 = Point1(x:0)
//    var p1 = Point1(x:0, y:0)
//    var p1 = Point1(y:0)

//成员无初始值,不可以使用无参初始化器,因为无参初始化器相当于什么事都没有做只是初始化,这样子成员会没有初始值,所以会直接报错
//    class Point2 {
//        var x: Int = 0
//        var y: Int
//    }//使用Point2()会报错
//    var p2 = Point2()

// 根据汇编可得知,下面2个初始化的形式[PointC2()和 PointC3()],完全等效
class PointC2 {
    var x: Int = 0
    var y: Int = 0
}
var C2 = PointC2()

//class PointC3 {
//    var x: Int
//    var y: Int
//    init() {
//        self.x = x
//        self.y = y
//    }
//}
//var C3 = PointC3()
//: ## 结构体与类的本质区别
// 结构体是值类型(枚举也是值类型), 类是引用类型(指针类型)
struct struct11 {
    var width = 1
    var height = 2
}//结构体的内存存在什么空间,取决与这个结构体放哪里,比如结构体是在方法里面的就一定是在栈空间,假如结构体是在最外层代码的相当于全局变量就是放在数据段,假如结构体是在类里面那就会随着类一起放在堆空间.枚举也同理
class class1 {
    var x = 3
    var y = 4
}//类的内存,不管这个类是在哪里创建的,类的内存始终的内容是一定存在堆空间
func test1() {
    var size1 = class1()
    var point1 = struct11()
}
/**
 在方法test1里面,方法的内容都是会存在栈空间,在test1这个方法的栈空间里,栈空间存放布局如下
 0-7:前8个字节存结构体的x,因为Int是8个字节
 8-15:紧接着的8个字节存结构体的y
 16-23:紧接着的8个字节存类的示例对象(size1)的内存地址[注意是内存地址,地址!因为类是引用类型(指针类型),指针占用8个字节(方便记:指8)]
    ps:在swift中,汇编中,如何判断这个方法的内存有没有在堆空间中,就看对应的汇编有没有出现 alloc 或者 malloc 这两个字眼的函数(分号;后面的注释内容)
 假设这个16-23存放的内容是0x10010这个地址,这个类的地址是指向堆空间,这个堆空间占用32个字节,前8个是指向类型信息,接着8个是引用计数.这前16个字节是固定的存储格式,后面的是存进来的内容,因为是存进来2个Int,所以是16,加起来一共是32
 */
//: ## 值类型
// 值类型赋值给var, let 或者给函数传参, 是直接将所有内容拷贝一份, 类似对文件的复制粘贴, 产生了全新的文件副本, 属于深拷贝(deep copy)
// 就是说赋值过后, 2者没有关系了, p1 = p2后, p1和p2各自怎么修改自己的值, 都不影响对方

/**
 汇编经验之谈(一般来说)
 1. 内存地址格式为 -0x10(%rbp),这种rbp减个数的,一般是局部变量(栈空间)
 2. 内存地址格式为 0x712a(%rip),这种rip加一个很大的数的,一般是全局变量,(全局变量的理解:程序运行中,内存地址只有一份,而且是固定死的,这种就叫全局变量)(全局区 || 数据段)
 3. 内存格式位:0x10(%rax),这种rax加一个数值的,因为堆空间的内存是动态生成的,所以刚好这种就很大几率是堆空间
   3.1 地址为1后面跟的不是4个0的数字的,一般是堆空间,比如105801b40
 4. func函数一搬放到代码段
 */

/*
 内存存放有4种类型区域,分布情况:最顶层最高也是最小是代码区(后四位最小,倒数第四位一般是数字),下一层是全局区(
 后四位是英文数字混合,倒数第四位一般是英文)全局区是紧跟代码区,下一层是是堆空间 和全局区间隔比较大,下一层是栈空间, 越下层内存地址越大
 1.堆空间: 放堆空间
 2.全局区: 放全局变量, 也有可能放对象内存的类型信息(matadata)[0x10000F048]10000后面4个字母和数字混合
 3.栈空间: 放局部变量
 4.代码区: 存放代码 [0x100001db0]这种1开头中间4个零接着1d开头的,一般都是代码区,地址值一般比较小
 
 */

// 值类型的赋值操作,(值类型的赋值属于深度拷贝,就是拷贝完成后,2者互不干扰)
//字符串
var s1 = "Jack"
var s2 = s1
s2.append("_rose") //append:拼接的意思
print(s1) //jack
print(s2) //jack_rose

//数组
var a1 = [1, 2, 3]
var a2 = a1
a2.append(4) // 数组的添加字段也是这样子拼接
a1[0] = 0
print(a1) // 0,2,3
print(a2) // 1,2,3,4

//字典
var d1 = ["max" : 10, "min" : 2]
var d2 = d1
var d3 = d1
d1["other"] = 7 // d1新增了other这个字段
d2["max"] = 5 //d2修改了原有的max这个字段
print(d1) // ["min": 2, "max": 10, "other": 7]
print(d2) // ["min": 2, "max": 5]
print(d3) // ["min": 2, "max": 10]

// 在swift标准库中,为了提升性能,String, Array, Dictionaay, Set采取了Copy On Write的技术.意思就是比如var s1 = "Jack",var s2 = s1.这串代码中,s2 = s1,假如后续s1和s2都没有被操作修改,那么系统就会自动浅拷贝,相当于此时s1和s2是指向同一块内存. 假如s1或者s2后续会被操作,那么就会执行的是深度拷贝,也就是值拷贝,s1和s2是两块完全不同的内存
// 建议: 不需要修改的, 尽量定义成 let
//: ## 引用类型
// 引用类型赋值给var, let, 函数 传参时, 是将内存地址拷贝一份,就说是引用类型的赋值(=), 其实就说地址值的操作
// 类似与电脑制作一个文件的替身(快捷方式), 指向的是同一个文件. 这种属于浅拷贝(shallow copy)
class Size1 {
    var width: Int
    var height: Int
    init(width: Int, height: Int) {
        self.width = width
        self.height = height
    }
}
func test11() {
    var s1 = Size1(width: 10, height: 20)
    var s2 = s1
}
/**
 s1和s2的内存地址分布
 因为是在方法func里面,所以s1和s2都是栈空间
  s1先是赋值了class Size1,这个是类,类似引用类型,所以s1现在实际存的内容是一个地址值(地址10),自身所占内存空间是(地址12)
  s2被s1赋值了,所以现在s2存的内容是和s1一样的同样是一个地址值(地址10),自身所占内存空间是和s1紧挨着的(地址1,栈空间中先到的地址大,后到的在高层,先到的在底层,所以s2是小地址,s1是比s2大8字节的地址,因为地址指针占8位)
 */
//: ## 寄存器(关于汇编的小笔记)
/**
一, 有16个常用的寄存器
  1. rax, rbx, rcx, rdx, rsi, rdi, rbp, rsp
  2. r8, r9, r10, r11, r12, r13, r14, r15
二, 寄存器的具体作用
  1. rax常作为函数的返回值使用
  2. rdi, rsi, rdx, rcx, r8, r9 等寄存器常用于存放函数的参数
  3. rsp, rbp 用作于栈操作
  4. rip作为指令指针
    4.1 存储着cpu下一条要执行的指令的地址
    4.2 一旦cpu读取了一条指令,rip会自动指向下一条指令(存储下一条指令的地址)
 
 关于堆空间
 一, 只要看到汇编的方法备注是alloc/ init/ allocating_init 这些字眼,就一定是系统在开辟堆空间来创建一个对象并且初始化这个对象
 二, 内存格式位:0x10(%rax),这种rax加一个数值的,因为堆空间的内存是动态生成的,所以刚好这种就很大几率是堆空间
 */
//: ## 值类型/ 引用类型的let
struct zhi {
    var x: Int
    var y: Int
}

class yinyong {
    var width: Int
    var height: Int
    init(width: Int, height: Int) {
        self.width = width
        self.height = height
    }
}

let z = zhi(x: 10, y: 20)
//z = zhi(x: 11, y: 22) z是结构体,是值类型,而且let的p是常量,p的内存实际存的是16个字节,x和y各8.重新初始化意味着要对这16个字节做修改,所以报错
//z.x = 12 对前8个字节修改,所以报错
//z.y = 13 对后8个字节修改,所以报错

let c = yinyong(width: 10, height: 20)
//c = yinyong(width: 11, height: 22)  c是类,类是引用类型,c是被let修饰的常量,c的内存内容存储的是yingyong所指向的堆空间的地址值,所以假如第二次yinyong(...去再初始化,这个内存内容存的这个地址值就会变,所以报错
c.width = 12 //不报错,因为这个是c的内存内容存的地址值,这个地址值是指向堆空间的一个地址,c.width是存在堆空间的这个地址的内容,所以改变了也不影响c的内容的内容(堆空间的地址值),所以不报错
c.height = 13//不报错,因为这个是c的内存内容存的地址值,这个地址值是指向堆空间的一个地址,c.height是存在堆空间的这个地址的内容,所以改变了也不影响c的内容的内容(堆空间的地址值),所以不报错
//: ## 嵌套类型
//需求:表达一副扑克牌,里面有花型,有大小等级
struct Poker {//扑克牌
    //花型
    enum Suit : Character {
        case spades = "♠", heart = "❤", diamond = "⭐️", clubs = "❀"
    }
    //等级大小
    enum Rank : Int {
        case two = 2, three, four, five, six, seven, eight, nine, ten
        case jack, queen, king, ace
    }
}
//Suit 和 Rank 就相当于Poker这个结构体里面的的嵌套类型,其实就是在内部的别的类型
print(Poker.Suit.diamond.rawValue)

var suit = Poker.Suit.spades
suit = .heart //拿到嵌套类型的类型后,使用起来和正常的操作没有区别
//: ## 枚举/ 结构体/ 类 都可以定义方法
// 一搬把定义在枚举, 结构体, 类 内部的函数, 叫做方法. (func在全局外面直接写的时候,叫做函数,在这3个类型里面写,就叫做方法了.其实方法和函数,本质是一样的)
// 方法是不占用实例对象的内存的,也就是说实例对象的内存里面不会有字节去存放方法,方法的本质就是函数. 所以方法或者函数, 都是存放到代码段
class Size2 {
    var width = 10
    var height = 20
    
    func show() { //show在size2里面,就叫做方法
        print("width = \(width), height = \(height)")
    }
}
let s = Size2() // 创建一个类的实例, 再利用这个实例去调用类里面的方法
s.show() //"width = 10, height = 20"

enum PokerFace : Character {
    case spades = "♠", heart = "❤", diamond = "⭐️", clubs = "❀"
    
    func show() {
        print("face is \(rawValue)") //枚举的rawValue是可以直接在枚举里面使用的
    }
}
let p = PokerFace.clubs //创建枚举的示例,初始化的时候需要写明枚举值
p.show() //face is ❀

//: [Next](@next)
